
package ibxm;

import java.io.*;
import javax.sound.sampled.*;

public class Player {
    private Thread play_thread;
    private IBXM ibxm;
    private Module module;
    private int song_duration, play_position;
    private boolean running, loop;
    private byte[] output_buffer;
    private SourceDataLine output_line;

    /**
     * Simple command-line test player.
     */
    public static void main(String[] args) throws Exception {
        if (args.length < 1) {
            System.err.println("Usage: java ibxm.Player <module file>");
            System.exit(0);
        }
        FileInputStream file_input_stream = new FileInputStream(args[0]);
        Player player = new Player();
        player.set_module(Player.load_module(file_input_stream));
        file_input_stream.close();
        player.play();
    }

    /**
     * Decode the data in the specified InputStream into a Module instance.
     *
     * @param input an InputStream containing the module file to be decoded.
     * @throws IllegalArgumentException if the data is not recognised as a module file.
     */
    public static Module load_module(InputStream input) throws IllegalArgumentException, IOException {
        DataInputStream data_input_stream = new DataInputStream(input);
        /* Check if data is in XM format.*/
        byte[] xm_header = new byte[60];
        data_input_stream.readFully(xm_header);
        if (FastTracker2.is_xm(xm_header))
            return FastTracker2.load_xm(xm_header, data_input_stream);
        /* Check if data is in ScreamTracker 3 format.*/
        byte[] s3m_header = new byte[96];
        System.arraycopy(xm_header, 0, s3m_header, 0, 60);
        data_input_stream.readFully(s3m_header, 60, 36);
        if (ScreamTracker3.is_s3m(s3m_header))
            return ScreamTracker3.load_s3m(s3m_header, data_input_stream);
        /* Check if data is in ProTracker format.*/
        byte[] mod_header = new byte[1084];
        System.arraycopy(s3m_header, 0, mod_header, 0, 96);
        data_input_stream.readFully(mod_header, 96, 988);
        return ProTracker.load_mod(mod_header, data_input_stream);
    }

    /**
     * Instantiate a new Player.
     */
    public Player() throws LineUnavailableException {
        ibxm = new IBXM(48000);
        set_loop(true);
        output_line = AudioSystem.getSourceDataLine(new AudioFormat(48000, 16, 2, true, true));
        output_buffer = new byte[1024 * 4];
    }

    /**
     * Set the Module instance to be played.
     */
    public void set_module(Module m) {
        if (m != null) module = m;
        stop();
        ibxm.set_module(module);
        song_duration = ibxm.calculate_song_duration();
    }

    /**
     * If loop is true, playback will continue indefinitely,
     * otherwise the module will play through once and stop.
     */
    public void set_loop(boolean loop) {
        this.loop = loop;
    }

    /**
     * Open the audio device and begin playback.
     * If a module is already playing it will be restarted.
     */
    public void play() {
        stop();
        play_thread = new Thread(new Driver());
        play_thread.start();
    }

    /**
     * Stop playback and close the audio device.
     */
    public void stop() {
        running = false;
        if (play_thread != null) {
            try {
                play_thread.join();
            } catch (InterruptedException ie) {
            }
        }
    }

    private class Driver implements Runnable {
        @Override
        public void run() {
            if (running) return;
            try {
                output_line.open();
                output_line.start();
                play_position = 0;
                running = true;
                while (running) {
                    int frames = song_duration - play_position;
                    if (frames > 1024) frames = 1024;
                    ibxm.get_audio(output_buffer, frames);
                    output_line.write(output_buffer, 0, frames * 4);
                    play_position += frames;
                    if (play_position >= song_duration) {
                        play_position = 0;
                        if (!loop) running = false;
                    }
                }
                output_line.drain();
                output_line.close();
            } catch (LineUnavailableException lue) {
                lue.printStackTrace();
            }
        }
    }
}
